using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using C5;
using CoreCalc;
using CoreCalc.SheetDefinedFunctions;
using CoreCalc.SheetDefinedFunctions.CompOnlyNeededCells;
using NUnit.Framework;

namespace TestCoreCalc.SheetDefinedFunctionsTest
{
    public class CodeGenTestSetup
    {
        internal class DoubleExpr : CGExpr
        {
            protected double val;

            public DoubleExpr(double val)
            {
                this.val = val;
            }

            public override void Compile(ILGenerator ilg)
            {
                new CGNumberConst(new NumberValue(val)).Compile(ilg);
            }

            public override void CompileToDouble(ILGenerator ilg, Generate g)
            {
                ilg.Emit(OpCodes.Ldc_R8, val);
                g.Gen(ilg);
            }

            public override StrictCellTypeHolder GetStrictCells()
            {
                return new StrictCellTypeHolder(false);
            }

            public override Typ Type()
            {
                return Typ.Number;
            }

            public override void CompileToDouble(ILGenerator ilg, Generate onSuccess, Generate onError)
            {
                CompileToDouble(ilg, onSuccess);
            }

            public override ConditionDictionary GetConditions()
            {
                throw new NotImplementedException();
            }
        }


        protected delegate double MethodToDouble(Value[] input);

        protected const double d1 = 2;
        protected const double d2 = 8;
        protected const double d3 = 5.55;
        protected const double d4 = 3.99;
        protected const double d5 = 2.11;

        protected const string s1 = "hello";
        protected const string s2 = "the bees knees";

        protected static CGExpr numbTrue = new CGNumberConst(numbValTrue);
        protected static CGExpr numbFalse = new CGNumberConst(numbValFalse);
        protected static CGExpr numbExpr1 = new CGNumberConst(numbVal1);
        protected static CGExpr numbExpr2 = new CGNumberConst(numbVal2);
        protected static CGExpr numbExpr3 = new CGNumberConst(numbVal3);
        protected static CGExpr numbExpr4 = new CGNumberConst(numbVal4);
        protected static CGExpr numbExpr5 = new CGNumberConst(numbVal5);
        protected DynamicMethod method;
        protected ILGenerator ilg;

        protected static NumberValue numbValTrue = new NumberValue(1);
        protected static NumberValue numbValFalse = new NumberValue(0);
        protected static NumberValue numbVal1 = new NumberValue(d1);
        protected static NumberValue numbVal2 = new NumberValue(d2);
        protected static NumberValue numbVal3 = new NumberValue(d3);
        protected static NumberValue numbVal4 = new NumberValue(d4);
        protected static NumberValue numbVal5 = new NumberValue(d5);

        protected static TextValue textVal1 = new TextValue(s1);
        protected static TextValue textVal2 = new TextValue(s2);

        protected static ErrorValue errArgCount = new ErrorValue(StandardError.ArgCount.ToString());
        protected static ErrorValue errArgType = new ErrorValue(StandardError.ArgType.ToString());
        protected static ErrorValue errNoSuchFunction = new ErrorValue(StandardError.NoSuchFunction.ToString());
        protected static ErrorValue errNumError = new ErrorValue(StandardError.NumError.ToString());

        protected void AddRet()
        {
            ilg.Emit(OpCodes.Ret);
        }

        protected SdfDelegate GetMethod()
        {
            return (SdfDelegate)method.CreateDelegate(typeof(SdfDelegate));
        }

        protected MethodToDouble GetDoubleMethod()
        {
            return (MethodToDouble)method.CreateDelegate(typeof(MethodToDouble));
        }

        protected void CheckIsNumberValue(Value output, double expected)
        {
            Assert.IsTrue(output is NumberValue);

            NumberValue val = output as NumberValue;

            if (val != null)
            {
                Assert.AreEqual(expected, val.value);
            }
        }

        protected void CheckIsNumberValue(Value output, double min, double max)
        {
            Assert.IsTrue(output is NumberValue);

            NumberValue val = output as NumberValue;

            if (val != null)
            {
                Assert.GreaterOrEqual(val.value, min);
                Assert.LessOrEqual(val.value, max);
            }
        }

        protected void CheckIsErrorValue(Value output, ErrorValue expected)
        {
            Assert.IsTrue(output is ErrorValue);

            ErrorValue val = output as ErrorValue;
            if (val != null)
            {
                //TODO: we don't know when this error is being catched and don't know which kind it is
                //Assert.AreEqual(expected.ToString(), val.ToString());
            }
        }

        protected void StartUp(Type returnVal)
        {
            method = new DynamicMethod("CGMethod", returnVal,
                        new Type[] { typeof(Value[]) }, typeof(CGFunction));
            ilg = method.GetILGenerator();
            CodeGenerate.SetLocalBuilderPools(100, ilg);
        }

        protected CGExpr[] GetVariables(Variable[] vars)
        {
            List<CGExpr> getVars = new List<CGExpr>();
            foreach (Variable var in vars)
            {
                CGExpr getVar = new CGTestCellRef(var);
                getVars.Add(getVar);
            }
            return getVars.ToArray();

        }

        protected double ExtractDouble(string cell, string sheetName, Workbook wb)
        {
            CellAddr ca = new CellAddr(cell);
            Sheet sheet = wb[sheetName];
            Cell c = sheet[ca];
            if (c == null) Assert.Fail("Empty cell (null)");
            //Value v = c.Eval(sheet, ca.col, ca.row);
            NumberValue result = sheet[ca].Eval(sheet, ca.col, ca.row) as NumberValue;
            if (result == null) Assert.Fail("Not a NumberValue");
            return result.value;
        }

    }

    //Old CGCellRef
    internal class CGTestCellRef : CGExpr
    {
        private Variable var;

        public CGTestCellRef(Variable var)
        {
            this.var = var;
        }

        public override void Compile(ILGenerator ilg)
        {
            ilg.Emit(OpCodes.Ldloc, var.Var);
        }
        
        public override void CompileToDouble(ILGenerator ilg, Generate g)
        {
            Compile(ilg);
            UnwrapToDouble(ilg, g, GenLoadStandardError(ilg, StandardError.NumError));
        }

        public override StrictCellTypeHolder GetStrictCells()
        {
            throw new NotImplementedException();
            //return new StrictCellTypeHolder(false);
        }

        public override string ToString()
        {
            return var.VariableName;
        }

        public override Typ Type()
        {
            return Typ.Value;
        }

        public override void GetReferencedCells(ref HashSet<FullCellAddr> localVars)
        {
            //localVars.Add(var);
        }

        public override void CompileToDouble(ILGenerator ilg, Generate onSuccess, Generate onError)
        {
            Compile(ilg);
            UnwrapToDouble(ilg, onSuccess, onError);
        }

        public override ConditionDictionary GetConditions()
        {
            throw new NotImplementedException();
        }
    }
}
